home *** CD-ROM | disk | FTP | other *** search
/ PC Format (PL) 2008 February / PC_Format_022008.iso / Internet / Mozilla Thunderbird wtyczki / lightning-0.7-tb-win.xpi / js / calCalendarManager.js < prev    next >
Encoding:
Text File  |  2007-09-23  |  27.1 KB  |  707 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Oracle Corporation code.
  15.  *
  16.  * The Initial Developer of the Original Code is Oracle Corporation
  17.  * Portions created by the Initial Developer are Copyright (C) 2005
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Stuart Parmenter <stuart.parmenter@oracle.com>
  22.  *   Matthew Willis <lilmatt@mozilla.com>
  23.  *   Michiel van Leeuwen <mvl@exedo.nl>
  24.  *   Martin Schroeder <mschroeder@mozilla.x-home.org>
  25.  *   Philipp Kewisch <mozilla@kewis.ch>
  26.  *
  27.  * Alternatively, the contents of this file may be used under the terms of
  28.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  29.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30.  * in which case the provisions of the GPL or the LGPL are applicable instead
  31.  * of those above. If you wish to allow use of your version of this file only
  32.  * under the terms of either the GPL or the LGPL, and not to allow others to
  33.  * use your version of this file under the terms of the MPL, indicate your
  34.  * decision by deleting the provisions above and replace them with the notice
  35.  * and other provisions required by the GPL or the LGPL. If you do not delete
  36.  * the provisions above, a recipient may use your version of this file under
  37.  * the terms of any one of the MPL, the GPL or the LGPL.
  38.  *
  39.  * ***** END LICENSE BLOCK ***** */
  40.  
  41. const SUNBIRD_UID = "{718e30fb-e89b-41dd-9da7-e25a45638b28}";
  42. const LIGHTNING_UID = "{e2fda1a4-762b-4020-b5ad-a41df1933103}";
  43.  
  44. const kStorageServiceContractID = "@mozilla.org/storage/service;1";
  45. const kStorageServiceIID = Components.interfaces.mozIStorageService;
  46.  
  47. const kMozStorageStatementWrapperContractID = "@mozilla.org/storage/statement-wrapper;1";
  48. const kMozStorageStatementWrapperIID = Components.interfaces.mozIStorageStatementWrapper;
  49. var MozStorageStatementWrapper = null;
  50.  
  51. function createStatement (dbconn, sql) {
  52.     var stmt = dbconn.createStatement(sql);
  53.     var wrapper = MozStorageStatementWrapper();
  54.     wrapper.initialize(stmt);
  55.     return wrapper;
  56. }
  57.  
  58. function onCalCalendarManagerLoad() {
  59.     MozStorageStatementWrapper = new Components.Constructor(kMozStorageStatementWrapperContractID, kMozStorageStatementWrapperIID);
  60. }
  61.  
  62. function calCalendarManager() {
  63.     this.wrappedJSObject = this;
  64.     this.initDB();
  65.     this.mCache = null;
  66.     this.mRefreshTimer = null;
  67.     this.setUpPrefObservers();
  68.     this.setUpRefreshTimer();
  69. }
  70.  
  71. var calCalendarManagerClassInfo = {
  72.     getInterfaces: function (count) {
  73.         var ifaces = [
  74.             Components.interfaces.nsISupports,
  75.             Components.interfaces.calICalendarManager,
  76.             Components.interfaces.nsIObserver,
  77.             Components.interfaces.nsIClassInfo
  78.         ];
  79.         count.value = ifaces.length;
  80.         return ifaces;
  81.     },
  82.  
  83.     getHelperForLanguage: function (language) {
  84.         return null;
  85.     },
  86.  
  87.     contractID: "@mozilla.org/calendar/manager;1",
  88.     classDescription: "Calendar Manager",
  89.     classID: Components.ID("{f42585e7-e736-4600-985d-9624c1c51992}"),
  90.     implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
  91.     flags: 0
  92. };
  93.  
  94. calCalendarManager.prototype = {
  95.     QueryInterface: function (aIID) {
  96.         if (aIID.equals(Components.interfaces.nsIClassInfo))
  97.             return calCalendarManagerClassInfo;
  98.  
  99.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  100.             !aIID.equals(Components.interfaces.calICalendarManager) &&
  101.             !aIID.equals(Components.interfaces.nsIObserver))
  102.         {
  103.             throw Components.results.NS_ERROR_NO_INTERFACE;
  104.         }
  105.  
  106.         return this;
  107.     },
  108.  
  109.     setUpPrefObservers: function ccm_setUpPrefObservers() {
  110.         var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
  111.                                 .getService(Components.interfaces.nsIPrefBranch2);
  112.         prefBranch.addObserver("calendar.autorefresh.enabled", this, false);
  113.         prefBranch.addObserver("calendar.autorefresh.timeout", this, false);
  114.     },
  115.  
  116.     setUpRefreshTimer: function ccm_setUpRefreshTimer() {
  117.         if (this.mRefreshTimer) {
  118.             this.mRefreshTimer.cancel();
  119.         }
  120.  
  121.         var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
  122.                                 .getService(Components.interfaces.nsIPrefBranch);
  123.  
  124.         var refreshEnabled = false;
  125.         try {
  126.             var refreshEnabled = prefBranch.getBoolPref("calendar.autorefresh.enabled");
  127.         } catch (e) {
  128.         }
  129.  
  130.         // Read and convert the minute-based pref to msecs
  131.         var refreshTimeout = 0;
  132.         try {
  133.             var refreshTimeout = prefBranch.getIntPref("calendar.autorefresh.timeout") * 60000;
  134.         } catch (e) {
  135.         }
  136.  
  137.         if (refreshEnabled && refreshTimeout > 0) {
  138.             this.mRefreshTimer = Components.classes["@mozilla.org/timer;1"]
  139.                                     .createInstance(Components.interfaces.nsITimer);
  140.             this.mRefreshTimer.init(this, refreshTimeout, 
  141.                                    Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
  142.         }
  143.     },
  144.     
  145.     observe: function ccm_observe(aSubject, aTopic, aData) {
  146.         if (aTopic == 'timer-callback') {
  147.             // Refresh all the calendars that can be refreshed.
  148.             var cals = this.getCalendars({});
  149.             for each (var cal in cals) {
  150.                 if (cal.canRefresh) {
  151.                     cal.refresh();
  152.                 }
  153.             }
  154.         } else if (aTopic == 'nsPref:changed') {
  155.             if (aData == "calendar.autorefresh.enabled" ||
  156.                 aData == "calendar.autorefresh.timeout") {
  157.                 this.setUpRefreshTimer();
  158.             }
  159.         }
  160.     },
  161.     
  162.     DB_SCHEMA_VERSION: 7,
  163.  
  164.     upgradeDB: function (oldVersion) {
  165.         // some common helpers
  166.         function addColumn(db, tableName, colName, colType) {
  167.             db.executeSimpleSQL("ALTER TABLE " + tableName + " ADD COLUMN " + colName + " " + colType);
  168.         }
  169.  
  170.         if (oldVersion <= 5 && this.DB_SCHEMA_VERSION >= 6) {
  171.             dump ("**** Upgrading calCalendarManager schema to 6\n");
  172.  
  173.             this.mDB.beginTransaction();
  174.             try {
  175.                 // Schema changes in v6:
  176.                 //
  177.                 // - Change all STRING columns to TEXT to avoid SQLite's
  178.                 //   "feature" where it will automatically convert strings to
  179.                 //   numbers (ex: 10e4 -> 10000). See bug 333688.
  180.  
  181.                 // Create the new tables.
  182.  
  183.                 try { 
  184.                     this.mDB.executeSimpleSQL("DROP TABLE cal_calendars_v6;" +
  185.                                               "DROP TABLE cal_calendars_prefs_v6;");
  186.                 } catch (e) {
  187.                     // We should get exceptions for trying to drop tables
  188.                     // that don't (shouldn't) exist.
  189.                 }
  190.  
  191.                 this.mDB.executeSimpleSQL("CREATE TABLE cal_calendars_v6 " +
  192.                                           "(id   INTEGER PRIMARY KEY," +
  193.                                           " type TEXT," +
  194.                                           " uri  TEXT);");
  195.  
  196.                 this.mDB.executeSimpleSQL("CREATE TABLE cal_calendars_prefs_v6 " +
  197.                                           "(id       INTEGER PRIMARY KEY," +
  198.                                           " calendar INTEGER," +
  199.                                           " name     TEXT," +
  200.                                           " value    TEXT);");
  201.  
  202.                 // Copy in the data.
  203.                 var calendarCols = ["id", "type", "uri"];
  204.                 var calendarPrefsCols = ["id", "calendar", "name", "value"];
  205.  
  206.                 this.mDB.executeSimpleSQL("INSERT INTO cal_calendars_v6(" + calendarCols.join(",") + ") " +
  207.                                           "     SELECT " + calendarCols.join(",") +
  208.                                           "       FROM cal_calendars");
  209.  
  210.                 this.mDB.executeSimpleSQL("INSERT INTO cal_calendars_prefs_v6(" + calendarPrefsCols.join(",") + ") " +
  211.                                           "     SELECT " + calendarPrefsCols.join(",") +
  212.                                           "       FROM cal_calendars_prefs");
  213.  
  214.  
  215.                 // Delete each old table and rename the new ones to use the
  216.                 // old tables' names.
  217.                 var tableNames = ["cal_calendars", "cal_calendars_prefs"];
  218.  
  219.                 for (var i in tableNames) {
  220.                     this.mDB.executeSimpleSQL("DROP TABLE " + tableNames[i] + ";" +
  221.                                               "ALTER TABLE " + tableNames[i] + "_v6 " + 
  222.                                               "  RENAME TO " + tableNames[i] + ";");
  223.                 }
  224.  
  225.                 this.mDB.commitTransaction();
  226.                 oldVersion = 6;
  227.             } catch (e) {
  228.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  229.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  230.                                              this.mDB.lastErrorString);
  231.                 this.mDB.rollbackTransaction();
  232.                 throw e;
  233.             }
  234.         }
  235.  
  236.         if (oldVersion != 6) {
  237.             dump ("#######!!!!! calCalendarManager Schema Update failed! " +
  238.                   " db version: " + oldVersion + 
  239.                   " this version: " + this.DB_SCHEMA_VERSION + "\n");
  240.             throw Components.results.NS_ERROR_FAILURE;
  241.         }
  242.     },
  243.  
  244.     initDB: function() {
  245.         var dbService = Components.classes[kStorageServiceContractID]
  246.                                   .getService(kStorageServiceIID);
  247.  
  248.         if ("getProfileStorage" in dbService) {
  249.             // 1.8 branch
  250.             this.mDB = dbService.getProfileStorage("profile");
  251.         } else {
  252.             // trunk
  253.             this.mDB = dbService.openSpecialDatabase("profile");
  254.         }
  255.  
  256.         var sqlTables = { cal_calendars: "id INTEGER PRIMARY KEY, type TEXT, uri TEXT",
  257.                           cal_calendars_prefs: "id INTEGER PRIMARY KEY, calendar INTEGER, name TEXT, value TEXT"
  258.                         };
  259.  
  260.         // Should we check the schema version to see if we need to upgrade?
  261.         var checkSchema = true;
  262.  
  263.         for (table in sqlTables) {
  264.             if (!this.mDB.tableExists(table)) {
  265.                 this.mDB.createTable(table, sqlTables[table]);
  266.                 checkSchema = false;
  267.             }
  268.         }
  269.  
  270.         if (checkSchema) {
  271.             // Check if we need to upgrade
  272.             var version = this.getSchemaVersion();
  273.             //dump ("*** Calendar schema version is: " + version + "\n");
  274.  
  275.             if (version < this.DB_SCHEMA_VERSION) {
  276.                 this.upgradeDB(version);
  277.             } else if (version > this.DB_SCHEMA_VERSION) {
  278.                 // Schema version is newer than what we know how to deal with.
  279.                 // Alert the user, and quit the app.
  280.                 var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  281.                                     .getService(Components.interfaces.nsIStringBundleService);
  282.  
  283.                 var brandSb = sbs.createBundle("chrome://branding/locale/brand.properties");
  284.                 var calSb = sbs.createBundle("chrome://calendar/locale/calendar.properties");
  285.  
  286.                 var hostAppName = brandSb.GetStringFromName("brandShortName");
  287.  
  288.                 // If we're Lightning, we want to include the extension name
  289.                 // in the error message rather than blaming Thunderbird.
  290.                 var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
  291.                                         .getService(Components.interfaces.nsIXULAppInfo);
  292.                 var errorBoxTitle;
  293.                 var errorBoxText;
  294.                 var errorBoxButtonLabel;
  295.                 if (appInfo.ID == SUNBIRD_UID) {
  296.                     errorBoxTitle = calSb.formatStringFromName("tooNewSchemaErrorBoxTitle",
  297.                                                                [hostAppName], 1);
  298.                     errorBoxText = calSb.formatStringFromName("tooNewSchemaErrorBoxTextSunbird",
  299.                                                               [hostAppName], 1);
  300.                     errorBoxButtonLabel = calSb.formatStringFromName("tooNewSchemaButtonQuit",
  301.                                                                      [hostAppName], 1);
  302.                 } else {
  303.                     lightningSb = sbs.createBundle("chrome://lightning/locale/lightning.properties");
  304.                     var calAppName = lightningSb.GetStringFromName("brandShortName");
  305.                     errorBoxTitle = calSb.formatStringFromName("tooNewSchemaErrorBoxTitle",
  306.                                                                [calAppName], 1);
  307.                     errorBoxText = calSb.formatStringFromName("tooNewSchemaErrorBoxTextLightning",
  308.                                                               [calAppName, hostAppName], 2);
  309.                     errorBoxButtonLabel = calSb.formatStringFromName("tooNewSchemaButtonRestart",
  310.                                                                      [hostAppName], 1);
  311.                 }
  312.  
  313.                 var promptSvc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  314.                                           .getService(Components.interfaces.nsIPromptService);
  315.  
  316.                 var errorBoxButtonFlags = promptSvc.BUTTON_POS_0 *
  317.                                           promptSvc.BUTTON_TITLE_IS_STRING +
  318.                                           promptSvc.BUTTON_POS_0_DEFAULT;
  319.  
  320.                 var choice = promptSvc.confirmEx(
  321.                                 null,
  322.                                 errorBoxTitle,
  323.                                 errorBoxText,
  324.                                 errorBoxButtonFlags,
  325.                                 errorBoxButtonLabel,
  326.                                 null, // No second button text
  327.                                 null, // No third button text
  328.                                 null, // No checkbox
  329.                                 {value: false}); // Unnecessary checkbox state
  330.  
  331.                 if (choice == 0) {
  332.                     var startup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
  333.                                             .getService(Components.interfaces.nsIAppStartup);
  334.                     if (appInfo.ID == SUNBIRD_UID) {
  335.                         startup.quit(Components.interfaces.nsIAppStartup.eForceQuit);
  336.                     } else {
  337.                         var em = Components.classes["@mozilla.org/extensions/manager;1"]
  338.                                            .getService(Components.interfaces.nsIExtensionManager);
  339.                         em.disableItem(LIGHTNING_UID);
  340.                         startup.quit(Components.interfaces.nsIAppStartup.eRestart | Components.interfaces.nsIAppStartup.eForceQuit);
  341.                     }
  342.                 }
  343.             }
  344.         }
  345.  
  346.         this.mSelectCalendars = createStatement (
  347.             this.mDB,
  348.             "SELECT oid,* FROM cal_calendars");
  349.  
  350.         this.mRegisterCalendar = createStatement (
  351.             this.mDB,
  352.             "INSERT INTO cal_calendars (type, uri) " +
  353.             "VALUES (:type, :uri)"
  354.             );
  355.  
  356.         this.mUnregisterCalendar = createStatement (
  357.             this.mDB,
  358.             "DELETE FROM cal_calendars WHERE id = :id"
  359.             );
  360.  
  361.         this.mGetPref = createStatement (
  362.             this.mDB,
  363.             "SELECT value FROM cal_calendars_prefs WHERE calendar = :calendar AND name = :name");
  364.  
  365.         this.mDeletePref = createStatement (
  366.             this.mDB,
  367.             "DELETE FROM cal_calendars_prefs WHERE calendar = :calendar AND name = :name");
  368.  
  369.         this.mInsertPref = createStatement (
  370.             this.mDB,
  371.             "INSERT INTO cal_calendars_prefs (calendar, name, value) " +
  372.             "VALUES (:calendar, :name, :value)");
  373.  
  374.         this.mDeletePrefs = createStatement (
  375.             this.mDB,
  376.             "DELETE FROM cal_calendars_prefs WHERE calendar = :calendar");
  377.     },
  378.  
  379.     /** 
  380.      * @return      db schema version
  381.      * @exception   various, depending on error
  382.      */
  383.     getSchemaVersion: function calMgrGetSchemaVersion() {
  384.         var stmt;
  385.         var version = null;
  386.  
  387.         try {
  388.             stmt = createStatement(this.mDB,
  389.                     "SELECT version FROM cal_calendar_schema_version LIMIT 1");
  390.             if (stmt.step()) {
  391.                 version = stmt.row.version;
  392.             }
  393.             stmt.reset();
  394.  
  395.             if (version !== null) {
  396.                 // This is the only place to leave this function gracefully.
  397.                 return version;
  398.             }
  399.         } catch (e) {
  400.             if (stmt) {
  401.                 stmt.reset();
  402.             }
  403.             dump ("++++++++++++ calMgrGetSchemaVersion() error: " +
  404.                   this.mDB.lastErrorString + "\n");
  405.             Components.utils.reportError("Error getting calendar " +
  406.                                          "schema version! DB Error: " + 
  407.                                          this.mDB.lastErrorString);
  408.             throw e;
  409.         }
  410.  
  411.         throw "cal_calendar_schema_version SELECT returned no results";
  412.     },
  413.  
  414.     notifyObservers: function(functionName, args) {
  415.         function notify(obs) {
  416.             try { obs[functionName].apply(obs, args);  }
  417.             catch (e) { }
  418.         }
  419.         this.mObservers.forEach(notify);
  420.     },
  421.  
  422.     /**
  423.      * calICalendarManager interface
  424.      */
  425.     createCalendar: function(type, uri) {
  426.         var calendar = Components.classes["@mozilla.org/calendar/calendar;1?type=" + type].createInstance(Components.interfaces.calICalendar);
  427.         calendar.uri = uri;
  428.         return calendar;
  429.     },
  430.  
  431.     registerCalendar: function(calendar) {
  432.         // bail if this calendar (or one that looks identical to it) is already registered
  433.         if (calendar.id > 0) {
  434.             dump ("registerCalendar: calendar already registered\n");
  435.             throw Components.results.NS_ERROR_FAILURE;
  436.         }
  437.  
  438.         this.assureCache();
  439.  
  440.         // Add an observer to track readonly-mode triggers
  441.         var newObserver = new errorAnnouncer(calendar);
  442.         calendar.addObserver(newObserver.observer);
  443.  
  444.         var pp = this.mRegisterCalendar.params;
  445.         pp.type = calendar.type;
  446.         pp.uri = calendar.uri.spec;
  447.  
  448.         this.mRegisterCalendar.step();
  449.         this.mRegisterCalendar.reset();
  450.         
  451.         calendar.id = this.mDB.lastInsertRowID;
  452.         
  453.         //dump("adding [" + this.mDB.lastInsertRowID + "]\n");
  454.         //this.mCache[this.mDB.lastInsertRowID] = calendar;
  455.         this.mCache[calendar.id] = calendar;
  456.  
  457.         this.notifyObservers("onCalendarRegistered", [calendar]);
  458.     },
  459.  
  460.     unregisterCalendar: function(calendar) {
  461.         this.notifyObservers("onCalendarUnregistering", [calendar]);
  462.  
  463.         var calendarID = calendar.id;
  464.  
  465.         var pp = this.mUnregisterCalendar.params;
  466.         pp.id = calendarID;
  467.         this.mUnregisterCalendar.step();
  468.         this.mUnregisterCalendar.reset();
  469.  
  470.         // delete prefs for the calendar too
  471.         pp = this.mDeletePrefs.params;
  472.         pp.calendar = calendarID;
  473.         this.mDeletePrefs.step();
  474.         this.mDeletePrefs.reset();
  475.  
  476.         if (this.mCache) {
  477.             delete this.mCache[calendarID];
  478.         }
  479.     },
  480.  
  481.     deleteCalendar: function(calendar) {
  482.         /* check to see if calendar is unregistered first... */
  483.         /* delete the calendar for good */
  484.         if (this.mCache && (calendar.id in this.mCache)) {
  485.             throw "Can't delete a registered calendar";
  486.         }
  487.         this.notifyObservers("onCalendarDeleting", [calendar]);
  488.  
  489.         // XXX This is a workaround for bug 351499. We should remove it once
  490.         // we sort out the whole "delete" vs. "unsubscribe" UI thing.
  491.         //
  492.         // We only want to delete the contents of calendars from local
  493.         // providers (storage and memory). Otherwise we may nuke someone's
  494.         // calendar stored on a server when all they really wanted to do was
  495.         // unsubscribe.
  496.         if (calendar instanceof Components.interfaces.calICalendarProvider &&
  497.            (calendar.type == "storage" || calendar.type == "memory")) {
  498.             try {
  499.                 calendar.deleteCalendar(calendar, null);
  500.             } catch (e) {
  501.                 Components.utils.reportError("error purging calendar: " + e);
  502.             }
  503.         }
  504.     },
  505.  
  506.     getCalendars: function cmgr_getCalendars(count) {
  507.         this.assureCache();
  508.         var calendars = [];
  509.         for each (var cal in this.mCache) {
  510.             calendars.push(cal);
  511.         }
  512.         count.value = calendars.length;
  513.         return calendars;
  514.     },
  515.  
  516.     assureCache: function cmgr_assureCache() {
  517.         if (!this.mCache) {
  518.             this.mCache = {};
  519.  
  520.             var stmt = this.mSelectCalendars;
  521.             stmt.reset();
  522.             var newCalendarData = [];
  523.             while (stmt.step()) {
  524.                 newCalendarData.push( { id: stmt.row.id, type: stmt.row.type, uri: stmt.row.uri } );
  525.             }
  526.             stmt.reset();
  527.  
  528.             for each (var caldata in newCalendarData) {
  529.                 try {
  530.                     var cal = this.createCalendar(caldata.type, makeURL(caldata.uri));
  531.                     cal.id = caldata.id;
  532.                     var newObserver = new errorAnnouncer(cal);
  533.                     cal.addObserver(newObserver.observer);
  534.                     this.mCache[caldata.id] = cal;
  535.                 } catch (exc) {
  536.                     Components.utils.reportError(
  537.                         "Can't create calendar for " + caldata.id +
  538.                         " (" + caldata.type + ", " + caldata.uri + "): " + exc);
  539.                 }
  540.             }
  541.         }
  542.     },
  543.  
  544.     getCalendarPref: function(calendar, name) {
  545.         // pref names must be lower case
  546.         name = name.toLowerCase();
  547.  
  548.         var stmt = this.mGetPref;
  549.         stmt.reset();
  550.         var pp = stmt.params;
  551.         pp.calendar = calendar.id;
  552.         pp.name = name;
  553.  
  554.         var value = null;
  555.         if (stmt.step()) {
  556.             value = stmt.row.value;
  557.         }
  558.         stmt.reset();
  559.         return value;
  560.     },
  561.  
  562.     setCalendarPref: function(calendar, name, value) {
  563.         var oldvalue = this.getCalendarPref(calendar, name);
  564.         if (oldvalue == value) {
  565.             // Only modify the preference if it changed.
  566.             return;
  567.         }
  568.  
  569.         // pref names must be lower case
  570.         name = name.toLowerCase();
  571.  
  572.         var calendarID = calendar.id;
  573.  
  574.         this.mDB.beginTransaction();
  575.  
  576.         var pp = this.mDeletePref.params;
  577.         pp.calendar = calendarID;
  578.         pp.name = name;
  579.         this.mDeletePref.step();
  580.         this.mDeletePref.reset();
  581.  
  582.         pp = this.mInsertPref.params;
  583.         pp.calendar = calendarID;
  584.         pp.name = name;
  585.         pp.value = value;
  586.         this.mInsertPref.step();
  587.         this.mInsertPref.reset();
  588.  
  589.         this.mDB.commitTransaction();
  590.  
  591.         this.notifyObservers("onCalendarPrefChanged",
  592.                              [calendar, name, value, oldvalue]);
  593.     },
  594.  
  595.     deleteCalendarPref: function(calendar, name) {
  596.         // pref names must be lower case
  597.         name = name.toLowerCase();
  598.  
  599.         this.notifyObservers("onCalendarPrefDeleting", [calendar, name]);
  600.  
  601.         var calendarID = calendar.id;
  602.  
  603.         var pp = this.mDeletePref.params;
  604.         pp.calendar = calendarID;
  605.         pp.name = name;
  606.         this.mDeletePref.step();
  607.         this.mDeletePref.reset();
  608.     },
  609.     
  610.     mObservers: Array(),
  611.     addObserver: function(aObserver) {
  612.         if (this.mObservers.indexOf(aObserver) != -1)
  613.             return;
  614.  
  615.         this.mObservers.push(aObserver);
  616.     },
  617.  
  618.     removeObserver: function(aObserver) {
  619.         function notThis(v) {
  620.             return v != aObserver;
  621.         }
  622.         
  623.         this.mObservers = this.mObservers.filter(notThis);
  624.     }
  625. };
  626.  
  627. // This is a prototype object for announcing the fact that a calendar error has
  628. // happened and that the calendar has therefore been put in readOnly mode.  We
  629. // implement a new one of these for each calendar registered to the calmgr.
  630. function errorAnnouncer(calendar) {
  631.     this.calendar = calendar;
  632.     // We compare this to determine if the state actually changed.
  633.     this.storedReadOnly = calendar.readOnly;
  634.     var announcer = this;
  635.     this.observer = {
  636.         // calIObserver:
  637.         onStartBatch: function() {},
  638.         onEndBatch: function() {},
  639.         onLoad: function(calendar) {},
  640.         onAddItem: function(aItem) {},
  641.         onModifyItem: function(aNewItem, aOldItem) {},
  642.         onDeleteItem: function(aDeletedItem) {},
  643.         onError: function(aErrNo, aMessage) {
  644.             announcer.announceError(aErrNo, aMessage);
  645.         }
  646.     }
  647. }
  648.  
  649. errorAnnouncer.prototype.announceError = function(aErrNo, aMessage) {
  650.     var paramBlock = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
  651.                                .createInstance(Components.interfaces.nsIDialogParamBlock);
  652.     var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  653.                         .getService(Components.interfaces.nsIStringBundleService);
  654.     var props = sbs.createBundle("chrome://calendar/locale/calendar.properties");
  655.     var errMsg;
  656.     paramBlock.SetNumberStrings(3);
  657.     if (!this.storedReadOnly && this.calendar.readOnly) {
  658.         // Major errors change the calendar to readOnly
  659.         errMsg = props.formatStringFromName("readOnlyMode", [this.calendar.name], 1);
  660.     } else if (!this.storedReadOnly && !this.calendar.readOnly) {
  661.         // Minor errors don't, but still tell the user something went wrong
  662.         errMsg = props.formatStringFromName("minorError", [this.calendar.name], 1);
  663.     } else {
  664.         // The calendar was already in readOnly mode, but still tell the user
  665.         errMsg = props.formatStringFromName("stillReadOnlyError", [this.calendar.name], 1);
  666.     }
  667.  
  668.     // When possible, change the error number into its name, to
  669.     // make it slightly more readable.
  670.     var errCode = "0x"+aErrNo.toString(16);
  671.     const calIErrors = Components.interfaces.calIErrors;
  672.     // Check if it is worth enumerating all the error codes.
  673.     if (aErrNo & calIErrors.ERROR_BASE) {
  674.         for (var err in calIErrors) {
  675.             if (calIErrors[err] == aErrNo) {
  676.                 errCode = err;
  677.             }
  678.         }
  679.     }
  680.  
  681.     var message;    
  682.     switch (aErrNo) {
  683.         case calIErrors.CAL_UTF8_DECODING_FAILED:
  684.             message = props.GetStringFromName("utf8DecodeError");
  685.             break;
  686.         case calIErrors.ICS_MALFORMEDDATA:
  687.             message = props.GetStringFromName("icsMalformedError");
  688.             break;
  689.         default:
  690.             message = aMessage
  691.     }
  692.  
  693.     paramBlock.SetString(0, errMsg);
  694.     paramBlock.SetString(1, errCode);
  695.     paramBlock.SetString(2, message);
  696.  
  697.     this.storedReadOnly = this.calendar.readOnly;
  698.  
  699.     var wWatcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  700.                              .getService(Components.interfaces.nsIWindowWatcher);
  701.     wWatcher.openWindow(null,
  702.                         "chrome://calendar/content/calErrorPrompt.xul",
  703.                         "_blank",
  704.                         "chrome,dialog=yes",
  705.                         paramBlock);
  706. }
  707.